﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;

namespace Gaming
{

    public interface ICardSet:IEnumerable<Card>
    {
        bool Contains(Card card);
        int Count {get;}
        Card this[int i] { get; }
        CardMask Mask { get; }
    }

    public interface ICardSetList : ICardSet
    {
        bool Contains(ICardSet set);
    }

    public class CardMask:ICardSet
    {
        internal ulong _mask;

        public ulong Code { get { return _mask; } set { _mask = value; } }

        public CardMask(ulong mask)
        {
            _mask = mask;
        }

        public CardMask(CardMask other)
        {
            _mask = other._mask;
        }

        public bool Contains(Card card)
        {
            ulong mask = 1ul << card.Code;
            return (_mask & mask) != 0;
        }

        public bool Contains(CardMask mask)
        {
            return (_mask & mask._mask) == mask._mask;
        }

        public bool Intersects(CardMask mask)
        {
            return (_mask & mask._mask) != 0;
        }

        public CardMask Mask { get { return this; } }

        public bool Contains(ICardSet set)
        {
            return Contains(set.Mask);
        }

        public CardMask Add(Card card)
        {
            _mask |= 1ul << card.Code;
            return this;
        }

        public CardMask Add(CardSet cset)
        {
            _mask |= cset.Mask._mask;
            return this;
        }

        public CardMask Remove(Card card)
        {
            _mask &= ~(1ul<<card.Code);
            return this;
        }

        public CardMask Remove(CardSet cset)
        {
            _mask &= ~cset.Mask.Code;
            return this;
        }

        public static bool operator==(CardMask a, CardMask b){return a._mask==b._mask;}
        public static bool operator !=(CardMask a, CardMask b) { return a._mask != b._mask; }

        #region ICardSet Members

        bool ICardSet.Contains(Card card)
        {
            throw new NotImplementedException();
        }

        int ICardSet.Count
        {
            get { throw new NotImplementedException(); }
        }

        Card ICardSet.this[int i]
        {
            get { throw new NotImplementedException(); }
        }

        #endregion

        #region IEnumerable<Card> Members

        IEnumerator<Card> IEnumerable<Card>.GetEnumerator()
        {
            ulong mask = 1ul;
            for (int i = Card.Min.Code; i <= Card.Max.Code; i++)
                if (Card.IsValidCode(i))
                    if ((_mask & (1ul << i)) != 0)
                        yield return Card.FromCode(i);
        }

        #endregion

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            return ((IEnumerable<Card>)this).GetEnumerator();
        }

        #endregion
    }

    /// <summary>
    /// Set of different unique cards
    /// </summary>
    public class CardSet : IEnumerable<Card>, IEquatable<ICardSet>,IEquatable<CardSet>, ICardSet
    {
        #region Fields
        /// <summary>
        /// array of card codes, see <see cref="card coding"/>
        /// </summary>
        internal Card[] _cards;
        protected bool _isCopy;
        
        internal CardSet _next;

        protected static CardSet _all52 = null;

        /// <summary>
        /// start index of set in array
        /// </summary>
        internal int _start;

        /// <summary>
        /// end index of set in array
        /// </summary>
        internal int _count;

        internal CardMask _mask;

        #endregion
        /// <summary>
        /// Compares cards by rank A>K>Q>J>T>10>9>8...
        /// Larger ranks first
        /// </summary>
        internal class RankComparer : IComparer
        {
            public int Compare(object x, object y)
            {
                Card a = (Card)x;
                Card b = (Card)y;
                if (a.Rank > b.Rank)
                    return -1;
                if (a.Rank < b.Rank)
                    return 1;
                return b.Suit - a.Suit;
            }
        }

        #region Constructors

        /// <summary>
        /// Constructs CardSet from given codes array
        /// </summary>
        /// <param name="set">Card codes array</param>
        /// <param name="start">Start index in the array</param>
        /// <param name="count">Count of codes to use</param>
        /// <param name="copy">Copy codes to internal buffer. If false, uses given array</param>
        public CardSet(Card[] set, int start, int count, bool copy):this()
        {
            if (copy)
            {
                Resize(_count = count);
                Array.Copy(set, start, _cards, 0, count);
                Sort();
            }
            else
            {
                _cards = set;
                _start = start;
                _count = count;
                _isCopy = false;
            }
            UpdateMask();
        }

        public CardSet(CardSet set):this()
        {
            CloneFrom(set);
        }
        /// <summary>
        /// Constructs empty (and hence invalid) cardset with given length
        /// </summary>
        /// <param name="size">Number of cards in the card set</param>
        public CardSet(int size)
            : this()
        {
            _cards = new Card[_count = size];
            _isCopy = true;
            _start = 0;
        }

        public CardSet()
        {
            _mask = new CardMask(0);
        }

        public void Resize(int size)
        {
            _isCopy = true;
            _cards = new Card[size];
            _count = size;
            _start = 0;
        }

        public void Verify()
        {
            CardMask mask = new CardMask(0);
            for (int i = _start; i < _start + _count; i++)
                if (mask.Contains(_cards[i]))
                    throw new InvalidOperationException();
                else
                    mask.Add(_cards[i]);
        }

        public void Sort()
        {
            if (!_isCopy)
                throw new InvalidOperationException();
            Array.Sort(_cards, new RankComparer());
        }

        internal Card[] cards { get { return _cards; } }

  

        public void Add(Card card)
        {
            _mask.Add(card);
        }

        public void Remove(Card card)
        {
            _mask.Remove(card);
        }

        public static CardSet From2Cards(Suits s1, Ranks r1, Suits s2, Ranks r2)
        {
            CardSet set = new CardSet(2);
            set[0] = Card.FromSuitRank((int)s1, (int)r1);
            set[1] = Card.FromSuitRank((int)s2, (int)r2);
            return set;
        }

        #endregion

        #region Properties
        public CardSet Next { get { return _next; } set { _next = value; } }
        public Card Min { get { return _cards[_start+_count-1]; } }
        public Card Max { get { return _cards[_start]; } }

        public CardMask Mask { get { return _mask; } }

        public void UpdateMask()
        {
            _mask._mask = 0;
            int end = _start + _count;
            for (int i = _start; i < end; i++)
            {
                // if (_set[i] > 63 || _set[i]<0)
                //     throw new InvalidOperationException();
                int next = _cards[i].Code;
                ulong add = (1ul << next);
                if(false)
                    if (next != 0 && 0 != (_mask._mask & add))
                        throw new InvalidOperationException(string.Format("Card {0} is already in the set {1}", _cards[i], this));
                _mask._mask |= add;
            }
        }

        /// <summary>
        /// returns count of cards in the cardset
        /// </summary>
        public int Count { get { return _count; } set { Resize(value); } }


        public IEnumerable<CardSet> GetCombinations(int size)
        {
            throw new NotImplementedException();
        }

        private void Enumerate()
        {
         //   CardTable ct = new CardTable(
        }

        public bool IsReady(int maxReadyIndex)
        {
            return maxReadyIndex >= _start + _count-1;
        }

        /// <summary>
        /// returns Card object representing a card with given index
        /// </summary>
        /// <param name="idx">0-based index of card to return</param>
        /// <returns>Card at the index</returns>
        public Card this[int idx] 
        { 
            get 
            { 
                return _cards[_start+idx]; 
            } 
            set 
            { 
                _cards[_start+idx] = value;
                UpdateMask();
            } 
        }

        public CardSet GetCardSet(int start, int count, bool copy)
        {
            return new CardSet(_cards, start, count, copy);
        }

        #endregion

        /// <summary>
        /// Parse combination.
        /// Samples:
        /// As7sQh
        /// </summary>
        /// <param name="input"></param>
        /// <param name="pos"></param>
        /// <returns></returns>
        public static CardSet Parse(string input, ref int pos)
        {
            Card[] cards = new Card[input.Length];
            int count = 0;
            while (pos<input.Length)
            {
                Card next = Card.Parse(input, ref pos);
                if (Card.Empty == next)
                    throw new InvalidOperationException();
                cards[count++] = next;
            }
            return new CardSet(cards, 0, count, true);
        }


        public static CardSet All52
        {
            get
            {
                if (null == _all52)
                {
                    _all52 = new CardSet(52);
                    int index = 0;
                    for (int rank = (int)Ranks.Ace; rank >= (int)Ranks.Two; rank--)
                        for (int suit = 0; suit <= 3; suit++)
                        {
                            _all52._cards[index++] = Card.FromSuitRank(suit, rank);
                        }
                    _all52.Sort();
                    _all52.UpdateMask();
                }
                return _all52;
            }
        }

        public void CopyFrom(CardSet other)
        {
            if (Count != other.Count)
                throw new InvalidOperationException();
            for (int i = 0; i < _count; i++)
                _cards[_start+i] = other._cards[other._start + i];

            UpdateMask();
        }

        public void CopyTo(Card[] cards, int start, int count)
        {
            for (int i = 0; i < count; i++)
                cards[start + i] = _cards[i];
        }

        public void CloneFrom(CardSet other)
        {
            _count = other._count;
            _isCopy = false;
            _start = other._start;
            _cards = other._cards;
            UpdateMask();
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            foreach (Card card in this)
            {
                if (sb.Length > 0) sb.Append(" ");
                sb.Append(card.ToString());
            }
            return string.Format("[{0}:{1}]",_start,_count)+sb.ToString();
        }

        public static CardSet Parse(string input)
        {
            int pos = 0;
            return Parse(input, ref pos);
        }

        #region Set operations

        /// <summary>
        /// Merges two or more cardsets. The cardsets should not intersect (shouldn't contain same cards)
        /// </summary>
        /// <param name="args">card sets to merge</param>
        /// <returns>merged cardset</returns>
        public static CardSet Merge(params CardSet[] args)
        {
            int count = 0;

            for (int i = 0; i < args.Length; i++)
                count += args[i].Count;

            Card[] set = new Card[count];

            int d = 0;

            for (int i = 0; i < args.Length; i++)
                for(int j=0;j<args[i].Count;j++)
                    set[d++] = args[i][j];

            return new CardSet(set, 0, count, true);
        }
        #endregion

        #region Hashcode
        public override int GetHashCode()
        {
            int code = 0;
            for (int i = 0; i < _count; i++)
                code = (code << 6) | _cards[i].Code;
            return code;
        }
        #endregion

        #region IEnumerable<Card> Members

        public IEnumerator<Card> GetEnumerator()
        {
            for (int i = _start; i < _start+_count; i++)
                yield return _cards[i];
        }

        #endregion

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            return (this as IEnumerable<Card>).GetEnumerator();
        }

        #endregion

        #region IEquatable<CardSet> Members

        public bool Equals(ICardSet other)
        {
            if (other is CardSet)
                return Equals(other as CardSet);

            if (_count != other.Count)
                return false;

            for (int i = 0; i < _count; i++)
                if (_cards[i+_start] != other[i])
                    return false;
            return true;
        }

        #endregion


        #region ICardSet Members

        public bool Contains(Card card)
        {
            for (int i = 0; i < Count; i++)
            {
                if (_cards[i] == card)
                    return true;
            }
            return false;
        }

        #endregion

        #region IEquatable<CardSet> Members

        public bool Equals(CardSet other)
        {
            // check by mask
            //if (_mask != other._mask)
              //  return false;
            return _mask == other._mask;

            if (_count != other._count)
                return false;

            int a = _start;
            int b= other._start;
            for (int i = 0; i < _count; i++)
                if (_cards[a++] != other._cards[b++])
                    return false;
            return true;

        }

        #endregion
    }

    /// <summary>
    /// List of cardsets
    /// </summary>
    public class CardSetList : ICardSetList
    {
        #region Fields
        private CardSet[] _sets;
        #endregion
        private int _countUnique;
        private CardMask _mask;

        #region Constructors
        public CardSetList(int size)
        {
            _mask = new CardMask(0);
            _sets = new CardSet[size];
        }

        public CardSetList(IEnumerable<CardSet> src)
        {
            _mask = new CardMask(0);

            int count = 0;
            foreach (CardSet set in src)
                count++;

            _sets = new CardSet[count];

            _countUnique = 0;
            _mask = new CardMask(0);
            int i = 0;
            
            foreach (CardSet set in src)
                _sets[i++] = set;

            Connect();

            UpdateMaskCount();
        }

        private void Connect()
        {
            for (int i = 0; i < _sets.Length - 1; i++)
            {
                _sets[i].Next = _sets[i + 1];
            }

            if (_sets.Length > 0)
                _sets[_sets.Length - 1].Next = null;
        }

        public static CardSetList Parse(params string[] arr)
        {
            List<CardSet> list = new List<CardSet>();
            foreach (string s in arr)
            {
                list.Add(CardSet.Parse(s));
            }
            return new CardSetList(list.ToArray());
        }

        public CardSetList(params CardSet[] args):this(args as IEnumerable<CardSet>)
        {
            
        }
        #endregion

        public void UpdateMaskCount()
        {
            _countUnique = 0;
            _mask._mask = 0;
            foreach (CardSet set in _sets)
            {
                if (null != set)
                {
                    foreach (Card card in set)
                    {
                        //if (card < 0 || card > 63)
                        //    throw new InvalidOperationException();
                        ulong add = 1ul << card.Code;

                        if ((_mask._mask & add) != 0)
                            _countUnique++;
                        
                        _mask._mask |= add;
                    }
                }
            }
        }
        #region Properties
        public int Length { get { return _sets.Length; } }

        public int Count { get { return _countUnique; } }

        public CardMask Mask { get { return _mask; } }

        public CardSet this[int idx] 
        { 
            get 
            { 
                return _sets[idx]; 
            } 
            set 
            { 
                _sets[idx] = value;
                UpdateMaskCount();
            } 
        }
        #endregion

        public bool Contains(CardSet some)
        {
            if (!Mask.Contains(some.Mask))
                return false;

            for (int i = 0; i < _sets.Length; i++)
                if (_sets[i].Equals(some))
                    return true;
            return false;
        }

        #region ICardSetList Members

        public bool Contains(ICardSet set)
        {
            if (set is CardSet)
                return Contains(set as CardSet);

            for (int i = 0; i < _sets.Length; i++)
            {
                if (_sets[i].Equals(set))
                    return true;
            }
            return false;
        }

        #endregion

        #region ICardSet Members

        public bool Contains(Card card)
        {
            return Mask.Contains(card);
        }

        Card ICardSet.this[int i]
        {
            get 
            {
                IEnumerator<Card> enu = this.GetEnumerator();
                int index = 0;
                while (enu.MoveNext())
                {
                    if (index++ == i)
                        return enu.Current;
                }
                throw new InvalidOperationException();
            }
        }

        #endregion

        #region IEnumerable<Card> Members

        public IEnumerator<Card> GetEnumerator()
        {
            for (int i = 0; i < _sets.Length; i++)
                for (int j = 0; j < _sets[i].Count; j++)
                    yield return _sets[i][j];
        }

        #endregion

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            return (this as IEnumerable<Card>).GetEnumerator();
        }

        #endregion

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < _sets.Length; i++)
            {
                if (sb.Length > 0)
                    sb.Append(" | ");
                sb.Append(this[i].ToString());
            }
            return "(" + sb.ToString() + ")";
        }
    }
}